﻿
// EntialiasingView.cpp: CEntialiasingView 类的实现
//

#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义，并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "Entialiasing.h"
#endif

#include "EntialiasingDoc.h"
#include "EntialiasingView.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CEntialiasingView

IMPLEMENT_DYNCREATE(CEntialiasingView, CView)

BEGIN_MESSAGE_MAP(CEntialiasingView, CView)
	// 标准打印命令
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
	ON_WM_LBUTTONDOWN()
	ON_WM_LBUTTONUP()
END_MESSAGE_MAP()

// CEntialiasingView 构造/析构

CEntialiasingView::CEntialiasingView() noexcept
{
}

CEntialiasingView::~CEntialiasingView()
{
}

BOOL CEntialiasingView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: 在此处通过修改
	//  CREATESTRUCT cs 来修改窗口类或样式

	return CView::PreCreateWindow(cs);
}

// CEntialiasingView 绘图

void CEntialiasingView::DrawLine()
{
	CDC* pDC = GetDC();

	// 两点在同一垂直线上
	if (pStart.x == pEnd.x)
	{
		DrawLineVertical(pDC);
	}
	// 两点在同一水平线上
	else if (pStart.y == pEnd.y)
	{
		DrawLineHorizontal(pDC);
	}
	else
	{
		// 保证pStart在pEND的左边
		// 将直线倾角控制在 -90°到 +90°
		if (pStart.x > pEnd.x)
		{
			CPoint2D temp = pStart;
			pStart = pEnd;
			pEnd = temp;
		}

		// 计算斜率
		int xDist = pEnd.x - pStart.x;
		int yDist = pEnd.y - pStart.y;
		float slope = float(yDist) / float(xDist);		

		// 如果斜率大于等于 1
		if (slope >= 1)
			DrawLineSlopeMoreThan1(pDC, slope);
		// 如果斜率在 -1 到 1之间
		else if (slope >= -1 && slope < 1)
			DrawLineSlopeBetweenNagt1WithPosi1(pDC, slope);
		// 如果斜率小于 -1
		else
			DrawLineSlopeLessThanNagt1(pDC, slope);
	}

	pDC->DeleteDC();
}

/*
* 两点在同一垂直线上，类似
	O_________________\ X
	|		
	|		pStart
	|       |
	|		|
	|		|
	|		|
	|		pEnd
   \|
    Y
*/
void CEntialiasingView::DrawLineVertical(CDC * pDC)
{
	// 保证pStart在pEnd的下面，方便后续遍历
	if (pStart.y > pEnd.y)
	{
		CPoint2D temp = pStart;
		pStart = pEnd;
		pEnd = temp;
	}

	for (int i = 1, yDist = pEnd.y - pStart.y; i < yDist; ++i)
	{
		CPoint2D point;
		point.x = pStart.x;
		point.y = pStart.y + i;
		
		// 当两点在同一垂直线时，就只需要BresenHam算法即可
		CalRGB(i, yDist, pStart.color, pEnd.color, point.color);

		pDC->SetPixel(point.x, point.y, RGB(point.color.red, point.color.green, point.color.blue));
	}
}

/*
* 两点在同一水平线上，类似
	O_________________\ X
	|
	|		
	|       
	|	pStart ________ pEnd
	|			
   \|
	Y
*/
void CEntialiasingView::DrawLineHorizontal(CDC * pDC)
{
	// 保证pStart在pEnd的下面，方便后续遍历
	if (pStart.x > pEnd.x)
	{
		CPoint2D temp = pStart;
		pStart = pEnd;
		pEnd = temp;
	}

	for (int i = 1, xDist = pEnd.x - pStart.x; i < xDist; ++i)
	{
		CPoint2D point;
		point.x = pStart.x + i;
		point.y = pStart.y;

		// 当两点在同一水平线时，就只需要BresenHam算法即可
		CalRGB(i, xDist, pStart.color, pEnd.color, point.color);

		pDC->SetPixel(point.x, point.y, RGB(point.color.red, point.color.green, point.color.blue));
	}
}

/*
* 直线斜率大于等于1，类似
	O_________________\ X
	|
	|		pStart
	|       \
	|		 \
	|		  \
	|		   \
	|		    pEnd
   \|
	Y
*
* 此时主方向为y方向
*/
void CEntialiasingView::DrawLineSlopeMoreThan1(CDC * pDC, float slope)
{
	float xStep = 1.0 / slope;		// xStep: y方向每增加1，x方向移动的距离
	float xReal = float(pStart.x);	// xReal: 记录每轮循环中当前理想点的x坐标
	
	// 主方向为y方向则遍历y
	for (int i = 1, yDist = pEnd.y - pStart.y; i < yDist; ++i)
	{
		CPoint2D pLeft, pRight;
		pLeft.y = pRight.y = pStart.y + i;	// 根据反走样算法，此时相邻两点y坐标相同

		xReal += xStep;						// 当前理想点的x坐标
		pLeft.x = floor(xReal);				// 位于理想点左边的点的x值
		pRight.x = ceil(xReal);				// 位于理想点右边的点的x值
		float ratio = xReal - pLeft.x;		// 理想点的x坐标与左边点的距离，用于反走样算法RGB值的计算

		CRGB cReal;
		CalRGB(i, yDist, pStart.color, pEnd.color, cReal);			// 首先根据BresenHam算法计算理想点的RGB值

		CalNeighborRGB(ratio, cReal, pLeft.color, pRight.color);	// 再根据反走样算法计算相邻两点的RGB值

		// 画点
		pDC->SetPixelV(pLeft.x, pLeft.y, RGB(pLeft.color.red, pLeft.color.green, pLeft.color.blue));
		pDC->SetPixelV(pRight.x, pRight.y, RGB(pRight.color.red, pRight.color.green, pRight.color.blue));
	}

}

/*
* 直线斜率在-1到1之间，类似
	O_________________\ X
	|
	|		pStart
	|       \
	|		    \
	|		        \
	|		            \
	|		               pEnd
   \|
	Y
*
* 此时主方向为x方向
*/
void CEntialiasingView::DrawLineSlopeBetweenNagt1WithPosi1(CDC * pDC, float slope)
{
	float yStep = 1.0 * slope;			// yStep: x方向每增加1，y方向移动的距离
	float yReal = float(pStart.y);		// yReal: 记录每轮循环中当前理想点的y坐标

	// 主方向为x方向则遍历x
	for (int i = 1, xDist = pEnd.x - pStart.x; i < xDist; ++i)
	{
		CPoint2D pBottom, pTop;
		pBottom.x = pTop.x = pStart.x + i;		// 根据反走样算法，此时相邻两点x坐标相同

		yReal += yStep;							// 当前理想点的y坐标
		pBottom.y = floor(yReal);				// 位于理想点下边的点的y值
		pTop.y = ceil(yReal);					// 位于理想点上边的点的y值
		float ratio = yReal - pBottom.y;		// 理想点的y坐标与下边点的距离，用于反走样算法RGB值的计算

		CRGB cReal;
		CalRGB(i, xDist, pStart.color, pEnd.color, cReal);			// 首先根据BresenHam算法计算理想点的RGB值

		CalNeighborRGB(ratio, cReal, pBottom.color, pTop.color);	// 再根据反走样算法计算相邻两点的RGB值

		// 画点
		pDC->SetPixelV(pBottom.x, pBottom.y, RGB(pBottom.color.red, pBottom.color.green, pBottom.color.blue));
		pDC->SetPixelV(pTop.x, pTop.y, RGB(pTop.color.red, pTop.color.green, pTop.color.blue));
	}
}

/*
* 直线斜率小于-1，类似
	O_________________\ X
	|
	|			pEnd
	|           /
	|		   /
	|		  /
	|		 /
	|	  pStart
   \|
	Y
*
* 此时主方向为y方向
*/
void CEntialiasingView::DrawLineSlopeLessThanNagt1(CDC * pDC, float slope)
{
	float xStep = 1.0 / slope;
	float xReal = float(pEnd.x);

	for (int i = 1, yDist = pStart.y - pEnd.y; i < yDist; ++i)
	{
		CPoint2D pLeft, pRight;
		pLeft.y = pRight.y = pEnd.y + i;

		xReal += xStep;
		pLeft.x = floor(xReal);
		pRight.x = ceil(xReal);
		float ratio = xReal - pLeft.x;

		CRGB cReal;
		CalRGB(i, yDist, pStart.color, pEnd.color, cReal);

		CalNeighborRGB(ratio, cReal, pLeft.color, pRight.color);

		pDC->SetPixelV(pLeft.x, pLeft.y, RGB(pLeft.color.red, pLeft.color.green, pLeft.color.blue));
		pDC->SetPixelV(pRight.x, pRight.y, RGB(pRight.color.red, pRight.color.green, pRight.color.blue));
	}
}

/*
* 与BresenHam算法一致，根据当前点与起始点和终止点的距离计算RGB值
*/
void CEntialiasingView::CalRGB(int x, int xDist, CRGB & cStart, CRGB & cEnd, CRGB & cCurr)
{
	float ratio = float(x) / float(xDist);

	cCurr.red = round((cEnd.red - cStart.red) * ratio) + cStart.red;
	cCurr.green = round((cEnd.green - cStart.green) * ratio) + cStart.green;
	cCurr.blue = round((cEnd.blue - cStart.blue) * ratio) + cStart.blue;
}

/*
* 反走样算法的精髓所在：根据当前理想点与相邻两点的距离计算RGB值
* 相邻两点的RGB值之和要等于理想点的RGB值
*/
void CEntialiasingView::CalNeighborRGB(float ratio, CRGB & cReal, CRGB & cLeftOrBottom, CRGB & cRightOrTop)
{
	cLeftOrBottom.red = round(cReal.red * ratio);
	cRightOrTop.red = cReal.red - cLeftOrBottom.red;

	cLeftOrBottom.green = round(cReal.green * ratio);
	cRightOrTop.green = cReal.green - cLeftOrBottom.green;

	cLeftOrBottom.blue = round(cReal.blue * ratio);
	cRightOrTop.blue = cReal.blue - cLeftOrBottom.blue;
}

void CEntialiasingView::OnDraw(CDC* /*pDC*/)
{
	CEntialiasingDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此处为本机数据添加绘制代码
}


// CEntialiasingView 打印

BOOL CEntialiasingView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// 默认准备
	return DoPreparePrinting(pInfo);
}

void CEntialiasingView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 添加额外的打印前进行的初始化过程
}

void CEntialiasingView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 添加打印后进行的清理过程
}


// CEntialiasingView 诊断

#ifdef _DEBUG
void CEntialiasingView::AssertValid() const
{
	CView::AssertValid();
}

void CEntialiasingView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

CEntialiasingDoc* CEntialiasingView::GetDocument() const // 非调试版本是内联的
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEntialiasingDoc)));
	return (CEntialiasingDoc*)m_pDocument;
}
#endif //_DEBUG


/*
* 鼠标左键按下，确定线段的起始点
*/
void CEntialiasingView::OnLButtonDown(UINT nFlags, CPoint point)
{	
	// 记录起始点
	pStart = CPoint2D(point.x, point.y, CRGB(255, 0, 0));

	// 制定画笔
	CDC* pDC = GetDC();
	CPen pen(PS_SOLID, 2, RGB(pStart.color.red, pStart.color.green, pStart.color.blue));
	pDC->SelectObject(&pen);

	// 在顶点处画圆
	pDC->Ellipse(pStart.x - 5, pStart.y - 5, pStart.x + 5, pStart.y + 5);

	// 释放资源
	pDC->DeleteDC();

	CView::OnLButtonDown(nFlags, point);
}

/*
* 鼠标左键松开，确定线段的终止点
*/
void CEntialiasingView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// 记录终止点
	pEnd = CPoint2D(point.x, point.y, CRGB(255, 0, 0));

	// 制定画笔
	CDC* pDC = GetDC();
	CPen pen(PS_SOLID, 2, RGB(pEnd.color.red, pEnd.color.green, pEnd.color.blue));
	pDC->SelectObject(&pen);

	// 在顶点处画圆
	pDC->Ellipse(pEnd.x - 5, pEnd.y - 5, pEnd.x + 5, pEnd.y + 5);

	// 终止点确定之后，开始画线
	DrawLine();

	// 释放资源
	pDC->DeleteDC();

	CView::OnLButtonUp(nFlags, point);
}
